﻿//******************************************************************************************************
//  DataSubscriberMeasurementMapper.cs - Gbtc
//
//  Copyright © 2013, Grid Protection Alliance.  All Rights Reserved.
//
//  Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
//  the NOTICE file distributed with this work for additional information regarding copyright ownership.
//  The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may
//  not use this file except in compliance with the License. You may obtain a copy of the License at:
//
//      http://www.opensource.org/licenses/eclipse-1.0.php
//
//  Unless agreed to in writing, the subject software distributed under the License is distributed on an
//  "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
//  License for the specific language governing permissions and limitations.
//
//  Code Modification History:
//  ----------------------------------------------------------------------------------------------------
//  05/07/2013 - Stephen C. Wills
//       Generated original version of source code.
//
//******************************************************************************************************

using System;
using System.Collections.Generic;
using System.Data;
using SIEGate.Core;
using SIEGate.Core.Adapters;
using SIEGate.Core.Legacy;
using SIEGate.Core.Measurements;
using SIEGate.Core.Measurements.Values;

namespace GSF.TimeSeries.Transport
{
    public class DataSubscriberMeasurementMapper : InputAdapter
    {
        #region [ Members ]

        // Nested Types
        public class AdapterInfo : AdapterInfoBase
        {
            /// <summary>
            /// Set the DataSource for this AdapterInfo class and derive member variables and properties relevant to this instance from that source.
            /// </summary>
            /// <param name="dataSource"></param>
            public override void ExtractConfiguration(DataSet dataSource)
            {
                base.ExtractConfiguration(dataSource);
                OutgoingStreamIDs.Clear();

                // If active measurements are defined, attempt to defined desired subscription points from there
                if (dataSource != null && dataSource.Tables.Contains("ActiveMeasurements"))
                {
                    try
                    {
                        // Filter to points associated with this subscriber that have been requested for subscription, are enabled and not owned locally
                        DataRow[] filteredRows = dataSource.Tables["ActiveMeasurements"].Select("Subscribed <> 0");
                        Guid signalID;

                        foreach (DataRow row in filteredRows)
                        {
                            // Parse primary measurement identifier
                            signalID = row["SignalID"].ToNonNullString(Guid.Empty.ToString()).ConvertToType<Guid>();
                            OutgoingStreamIDs.Add(signalID);
                        }
                    }
                    catch (Exception ex)
                    {
                        // Errors here may not be catastrophic, this simply limits the auto-assignment of input measurement keys desired for subscription
                        LogDispatcher.Log(LogLevel.Error, "DataSubscriber", "configuration", string.Format("Failed to define subscribed measurements: {0}{1}{2}", ex.Message, Environment.NewLine, ex.StackTrace));
                    }
                }
            }
        }

        // Constants
        private const MeasurementStateFlags DataRangeMask = MeasurementStateFlags.OverRangeError | MeasurementStateFlags.UnderRangeError | MeasurementStateFlags.AlarmHigh | MeasurementStateFlags.AlarmLow | MeasurementStateFlags.WarningHigh | MeasurementStateFlags.WarningLow;
        private const MeasurementStateFlags DataQualityMask = MeasurementStateFlags.BadData | MeasurementStateFlags.SuspectData | MeasurementStateFlags.FlatlineAlarm | MeasurementStateFlags.ComparisonAlarm | MeasurementStateFlags.ROCAlarm | MeasurementStateFlags.ReceivedAsBad | MeasurementStateFlags.CalculationError | MeasurementStateFlags.CalculationWarning | MeasurementStateFlags.ReservedQualityFlag;
        private const MeasurementStateFlags TimeQualityMask = MeasurementStateFlags.BadTime | MeasurementStateFlags.SuspectTime | MeasurementStateFlags.LateTimeAlarm | MeasurementStateFlags.FutureTimeAlarm | MeasurementStateFlags.UpSampled | MeasurementStateFlags.DownSampled | MeasurementStateFlags.ReservedTimeFlag;
        private const MeasurementStateFlags SystemIssueMask = MeasurementStateFlags.SystemError | MeasurementStateFlags.SystemWarning | MeasurementStateFlags.MeasurementError;
        private const MeasurementStateFlags CalculatedValueMask = MeasurementStateFlags.CalcuatedValue;
        private const MeasurementStateFlags DiscardedValueMask = MeasurementStateFlags.DiscardedValue;

        // Fields
        private long m_processedMeasurements;

        #endregion

        #region [ Constructors ]

        public DataSubscriberMeasurementMapper(IAdapterInfo conf)
            : base(conf)
        {
        }

        #endregion

        #region [ Properties ]

        public new AdapterInfo Info
        {
            get
            {
                return (AdapterInfo)base.Info;
            }
            set
            {
                base.Info = value;
            }
        }

        #endregion

        #region [ Methods ]

        public string GetShortStatus(int maxLength)
        {
            return string.Format("Processed measurements: {0}", m_processedMeasurements).CenterText(maxLength);
        }

        protected override void HandleMeasurement(IMeasurement m)
        {
            ICollection<CompactMeasurement> compactMeasurements = m.Value.GetRaw().Item1;
            ICollection<BufferBlockMeasurement> bufferBlockMeasurements = m.Value.GetRaw().Item2;

            if ((object)compactMeasurements != null)
                TranslateCompactMeasurements(compactMeasurements);

            if ((object)bufferBlockMeasurements != null)
                TranslateBufferBlockMeasurements(bufferBlockMeasurements);
        }

        private void TranslateCompactMeasurements(ICollection<CompactMeasurement> measurements)
        {
            Guid streamID;
            Ticks timestamp;
            MeasurementStateFlags flags;
            IMeasurementValue value;

            foreach (CompactMeasurement measurement in measurements)
            {
                // Do not publish measurements which are not
                // part of this adapter's outgoing stream IDs
                if (!OutgoingStreamIDs.Contains(measurement.SignalID))
                    continue;

                // Get necessary values to create measurement
                streamID = measurement.SignalID;
                timestamp = measurement.Timestamp;
                flags = MapToFullFlags(measurement.StateFlags);
                value = new DoubleValue(measurement.Value);

                // Emit measurement to message bus
                OnEmit(new Measurement(streamID, timestamp, flags, value));
                m_processedMeasurements++;
            }
        }

        private void TranslateBufferBlockMeasurements(ICollection<BufferBlockMeasurement> measurements)
        {
            Guid streamID;
            Ticks timestamp;
            MeasurementStateFlags flags;
            IMeasurementValue value;

            foreach (BufferBlockMeasurement measurement in measurements)
            {
                // Do not publish measurements which are not
                // part of this adapter's outgoing stream IDs
                if (!OutgoingStreamIDs.Contains(measurement.StreamID))
                    continue;

                // Get necessary values to create measurement
                streamID = measurement.StreamID;
                timestamp = DateTime.UtcNow.Ticks;
                flags = MeasurementStateFlags.Normal;
                value = new BufferBlockValue(measurement.Buffer, 0, measurement.Length);

                // Emit measurement to message bus
                OnEmit(new Measurement(streamID, timestamp, flags, value));
                m_processedMeasurements++;
            }
        }

        #endregion

        #region [ Static ]

        // Static Methods

        /// <summary>
        /// Maps <see cref="CompactMeasurementStateFlags"/> to <see cref="MeasurementStateFlags"/>.
        /// </summary>
        /// <param name="stateFlags">Flags to map.</param>
        /// <returns><see cref="MeasurementStateFlags"/> mapped from <see cref="CompactMeasurementStateFlags"/>.</returns>
        public static MeasurementStateFlags MapToFullFlags(CompactMeasurementStateFlags stateFlags)
        {
            MeasurementStateFlags mappedStateFlags = MeasurementStateFlags.Normal;

            if ((stateFlags & CompactMeasurementStateFlags.DataRange) > 0)
                mappedStateFlags |= DataRangeMask;

            if ((stateFlags & CompactMeasurementStateFlags.DataQuality) > 0)
                mappedStateFlags |= DataQualityMask;

            if ((stateFlags & CompactMeasurementStateFlags.TimeQuality) > 0)
                mappedStateFlags |= TimeQualityMask;

            if ((stateFlags & CompactMeasurementStateFlags.SystemIssue) > 0)
                mappedStateFlags |= SystemIssueMask;

            if ((stateFlags & CompactMeasurementStateFlags.CalculatedValue) > 0)
                mappedStateFlags |= CalculatedValueMask;

            if ((stateFlags & CompactMeasurementStateFlags.DiscardedValue) > 0)
                mappedStateFlags |= DiscardedValueMask;

            return mappedStateFlags;
        }

        #endregion
    }
}
